package edu.pku.sei.sct.primitives;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EReference;

import edu.pku.sei.AMT.Key;
import edu.pku.sei.AMT.LinkConstraint;
import edu.pku.sei.AMT.ObjectVariable;
import edu.pku.sei.AMT.Section;
import edu.pku.sei.ocl.evaluation.Context;
import edu.pku.sei.ocl.utilities.CacheUtil;
import edu.pku.sei.ocl.utilities.OCLStandardLibrary;
import edu.pku.sei.sct.evaluation.SCTEnvironment;
import edu.pku.sei.sct.util.ContextFrameWrapper;
import edu.pku.sei.sct.util.SectionSet;

public class SortVariables extends AbstractPrimitive {
	public static final String ID = "SortVariables";
	private CacheUtil sortedVariables = new CacheUtil();

	public SortVariables(SCTEnvironment e) {
		super(e);
	}

	/**
	 * argv[0] = sections
	 * argv[2] = context
	 */
	@SuppressWarnings("unchecked")
	@Override
	public Object execute(Object... argv) {
		List<ObjectVariable> oList = new ArrayList<ObjectVariable>();
		List<LinkConstraint> cList = new ArrayList<LinkConstraint>();
		
		
		List<Section> sections = (List<Section>)argv[0];
		Context context = (Context)argv[1];
		
		for(Section sec : sections ) {
			for(Object v : sec.getReferredVariables()) {
				if(v instanceof ObjectVariable)
					oList.add((ObjectVariable)v);
			}
			cList.addAll(sec.getLinks());
		}
		
		
		ContextFrameWrapper cfw = new ContextFrameWrapper(context);
		SectionSet sw = new SectionSet(sections);
		
		List<ObjectVariable> result = (List<ObjectVariable>) sortedVariables.get(sw,cfw);
		if(result==null) {
			int lastSize = 0;
			int currentSize = 0;
			
			lastSize = oList.size();
			currentSize = oList.size();
			result = new ArrayList<ObjectVariable>();
			
			sortedVariables.put(result, sw,cfw);
			
			//GetKeyExpressions gke = (GetKeyExpressions)this.getSctEnvironment().getPrimitive(GetKeyExpressions.ID);		
			
			do {
				if(currentSize==0) break;
				
				lastSize = currentSize;
				for(int i=currentSize-1;i>=0;i--) {
					ObjectVariable ov = oList.get(i);
					
					if(OCLStandardLibrary.oclIsValid(context.getValue(ov.getName()))){
						result.add(ov);
						oList.remove(i);
					} else {
						if(check(ov,result,cList)) {
							result.add(ov);
							oList.remove(i);
						}
					}
				}
				
				currentSize = oList.size();
				
			} while(currentSize!=lastSize);
			
			if(oList.size()!=0) {
				result.addAll(oList);
			}		
		}
		
		return result;
	}


	
	@SuppressWarnings("unchecked")
	private boolean check(ObjectVariable ov, List<ObjectVariable> result, List<LinkConstraint> cList) {
		EClass type = this.getSctEnvironment().getType(ov.getScope(), ov.getType());
		List<Key> keys = this.getSctEnvironment().getTransformationEnvironment().getTransformation().getKey(type);

		for(LinkConstraint lc : cList) {
			//String fn = lc.getRefName();
			EReference ref = this.getSctEnvironment().getReference(lc.getSource().getScope(), lc.getSource().getType(), lc.getRefName());

			if(lc.getSource()==ov) {
				if(isKeyFeature(ov,ref, keys, true)) {
					if(!result.contains(lc.getTarget())) {
						return false;
					}
				}
			} else if(lc.getTarget()==ov) {
				if(isKeyFeature(ov,ref, keys, false)) {
					if(!result.contains(lc.getSource())) {
						return false;
					}
				}
			}
		}
		return true;
	}

	private boolean isKeyFeature(ObjectVariable ov, EReference ref, List<Key> keys, boolean b) {
		EReference eOpposite = ref.getEOpposite();
		if(b) {
			for(Key k : keys) {
				for(Object kn : k.getFeatureNames()) {
					if(kn.equals("@"+ref.getName()))
						return true;
					else if(eOpposite!=null && kn.equals("!"+eOpposite.getName()))
						return true;
				}
			}
		} else {
			for(Key k : keys) {
				for(Object kn : k.getFeatureNames()) {
					if(kn.equals("!"+ref.getName()))
						return true;
					else if(eOpposite!=null && kn.equals("@"+eOpposite.getName()))
						return true;
				}
			}
		}
		return false;
	}

	@Override
	public String getID() {
		return ID;
	}

}
